package net.simpleframework.swing;

import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;

import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;

import net.simpleframework.swing.JActions.ActionCallback;
import net.simpleframework.swing.JTableExColumn.FilterExpression;
import net.simpleframework.swing.JTableExColumn.FilterItem;
import net.simpleframework.util.CSVWriter;
import net.simpleframework.util.ConvertUtils;
import net.simpleframework.util.LangUtils;
import net.simpleframework.util.LocaleI18n;
import net.simpleframework.util.StringUtils;

import org.apache.commons.lang3.ArrayUtils;
import org.jdesktop.swingx.JXButton;

/**
 * 这是一个开源的软件，请在LGPLv3下合法使用、修改或重新发布。
 * 
 * @author 陈侃(cknet@126.com, 13910090885)
 *         http://code.google.com/p/simpleframework/
 *         http://www.simpleframework.net
 */
public class JTableEx extends JAbstractTableEx {
	private static final long serialVersionUID = -468207631855254468L;

	{
		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(final MouseEvent e) {
				fireTablePopupMenu(e);
			}
		});

		getTableHeader().addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(final MouseEvent e) {
				fireTableHeaderPopupMenu(e);
			}
		});

		initKeyboardActions();
	}

	public JTableEx() {
	}

	public JTableEx(final Object[] columns) {
		setColumns(columns);
	}

	public JTableEx(final JTableExColumn[] columns) {
		setColumns(columns);
	}

	/***************************** Actions and PopupMenu *****************************/

	protected JTableExActions actions;

	private void initKeyboardActions() {
		final int c = JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT;
		if (actions == null) {
			actions = new JTableExActions(this);
		}
		registerKeyboardAction(actions.getTableStatisticsAction(), KeyStroke.getKeyStroke("S"), c);
		registerKeyboardAction(actions.getRefreshAction(), KeyStroke.getKeyStroke("F5"), c);
		registerKeyboardAction(actions.getCopyAction(), KeyStroke.getKeyStroke("ctrl C"), c);
		registerKeyboardAction(getActionMap().get("find"), KeyStroke.getKeyStroke("ctrl F"), c);
	}

	protected ActionCallback getDeleteActionCallback() {
		return null;
	}

	protected ActionCallback getEditActionCallback() {
		return null;
	}

	protected void fireTablePopupMenu(final MouseEvent e) {
		if (!SwingUtilities.isRightMouseButton(e)) {
			return;
		}
		final JPopupMenu menu = getPopupMenu();
		if (menu == null) {
			return;
		}
		SwingUtils.showPopupMenu(menu, JTableEx.this, e.getX(), e.getY());
	}

	protected void fireTableHeaderPopupMenu(final MouseEvent e) {
		if (!SwingUtilities.isRightMouseButton(e)) {
			return;
		}
		beforeTableHeaderPopupMenuShow(e);
		final JPopupMenu menu = getHeaderMenu();
		if (menu == null) {
			return;
		}
		SwingUtils.showPopupMenu(menu, getTableHeader(), e.getX(), e.getY());
	}

	JTableExColumn mTableExColumn;

	protected void beforeTableHeaderPopupMenuShow(final MouseEvent e) {
		mTableExColumn = getPrivateTableExColumn(getColumnModel().getColumnIndexAtX(e.getX()));
		if (mTableExColumn != null) {
			columnMenuItem.setText(LocaleI18n.getMessage("JTableEx.4") + " ( "
					+ mTableExColumn.getColumnName() + " )");
			// columnMenuItem.setText(mTableExColumn.getColumnText());
			actions.getDeleteFilterAction().setEnabled(mTableExColumn.getFilterExpression() != null);
		}
	}

	protected JPopupMenu getPopupMenu() {
		if (isFixTable()) {
			return ((JTableEx) dataTbl).getPopupMenu();
		}
		final JPopupMenu defaultPopupMenu = new JPopupMenu();
		defaultPopupMenu.add(actions.getTableStatisticsAction());
		defaultPopupMenu.addSeparator();
		boolean separator = false;
		if (getEditActionCallback() != null) {
			defaultPopupMenu.add(actions.getEditAction());
			separator = true;
		}
		if (getDeleteActionCallback() != null) {
			defaultPopupMenu.add(actions.getDeleteAction());
			separator = true;
		}
		if (separator) {
			defaultPopupMenu.addSeparator();
		}
		defaultPopupMenu.add(actions.getCopyAction());
		defaultPopupMenu.addSeparator();
		defaultPopupMenu.add(actions.getFindAction());
		defaultPopupMenu.addSeparator();
		final JMenu exports = new JMenu(LocaleI18n.getMessage("JTableEx.0"));
		exports.setIcon(SwingUtils.loadIcon("tbl-export.png"));
		defaultPopupMenu.add(exports);
		exports.add(actions.getExportCSVActionAction());
		defaultPopupMenu.addSeparator();
		final Action hsAction = actions.getHorizontalScrollAction();
		hsAction.putValue(Action.NAME, LocaleI18n
				.getMessage(isHorizontalScrollEnabled() ? "JTableExActions.17" : "JTableExActions.18"));
		defaultPopupMenu.add(hsAction);
		defaultPopupMenu.add(actions.getRefreshAction());
		return defaultPopupMenu;
	}

	protected final JMenuItem columnMenuItem = new JMenuItem();
	{
		columnMenuItem.setEnabled(false);
	}

	protected JPopupMenu getHeaderMenu() {
		if (mTableExColumn == null) {
			return null;
		}
		final JPopupMenu defaultHeaderMenu = new JPopupMenu();
		defaultHeaderMenu.add(columnMenuItem);
		defaultHeaderMenu.addSeparator();
		addDefaultHeaderMenu(defaultHeaderMenu);
		addFunctionHeaderMenu(defaultHeaderMenu);
		return defaultHeaderMenu;
	}

	protected void addDefaultHeaderMenu(final JPopupMenu headerMenu) {
		headerMenu.add(actions.getClearSortAction());
		headerMenu.add(actions.getSortASCAction());
		headerMenu.add(actions.getSortDESCAction());
		headerMenu.addSeparator();
		headerMenu.add(actions.getFilterAction());
		headerMenu.add(actions.getDeleteFilterAction());
		headerMenu.addSeparator();
		headerMenu.add(actions.getPackAllAction());
		headerMenu.addSeparator();
		final Action freezableAction = actions.getFreezableAction();
		freezableAction.putValue(Action.NAME,
				LocaleI18n.getMessage(!isFixTable() ? "JTableExActions.15" : "JTableExActions.16"));
		headerMenu.add(freezableAction);
	}

	protected void addFunctionHeaderMenu(final JPopupMenu headerMenu) {
		final Class<?> pt = mTableExColumn.getBeanPropertyType();
		if (pt != null && (Number.class.isAssignableFrom(pt) || Date.class.isAssignableFrom(pt))) {
			headerMenu.addSeparator();
			final JMenu functionMenu = new JMenu(LocaleI18n.getMessage("JTableEx.1"));
			if (Number.class.isAssignableFrom(pt)) {
				functionMenu.add(actions.getFunctionSumAction());
				functionMenu.add(actions.getFunctionAvgAction());
			}
			functionMenu.add(actions.getFunctionMaxAction());
			functionMenu.add(actions.getFunctionMinAction());
			headerMenu.add(functionMenu);
		}
	}

	/***************************** Filter *****************************/
	static Color filterHeaderColor = Color.decode("#BDF2D2");

	@Override
	protected void initHeaderRendererComponent(final JComponent rendererComponent, final int row,
			final int col) {
		super.initHeaderRendererComponent(rendererComponent, row, col);
		if (col == -1) {
			return;
		}
		final JTableExColumn column = getPrivateTableExColumn(col);
		if (column != null && column.getFilterExpression() != null) {
			rendererComponent.setBackground(filterHeaderColor);
			rendererComponent.setToolTipText(column.getFilterExpression().getTooltip());
		}
	}

	private boolean rowDelete(final FilterItem item, final Object v, final Class<?> pt) {
		final String r = item.relation;
		final Object v2 = item.value;
		if (("=").equalsIgnoreCase(r) && LangUtils.objectEquals(v, v2)) {
			return false;
		} else if (("<>").equalsIgnoreCase(r) && !LangUtils.objectEquals(v, v2)) {
			return false;
		} else if (("like").equalsIgnoreCase(r)
				&& StringUtils.blank(v).contains(StringUtils.blank(v2))) {
			return false;
		} else {
			if (Number.class.isAssignableFrom(pt)) {
				final double d = ConvertUtils.toDouble(v, Double.MIN_VALUE);
				final double d2 = ConvertUtils.toDouble(v2, Double.MIN_VALUE);
				if ((">".equalsIgnoreCase(r) && d > d2) || (">=".equalsIgnoreCase(r) && d >= d2)
						|| ("<".equalsIgnoreCase(r) && d < d2) || ("<=".equalsIgnoreCase(r) && d <= d2)) {
					return false;
				}
			} else if (Date.class.isAssignableFrom(pt)) {
				final Date d = (Date) v;
				final Date d2 = (Date) v2;
				if (d != null && d2 != null) {
					if ((">".equalsIgnoreCase(r) && d.after(d2))
							|| (">=".equalsIgnoreCase(r) && (d.after(d2) || d.equals(d2)))
							|| ("<".equalsIgnoreCase(r) && d.before(d2))
							|| ("<=".equalsIgnoreCase(r) && (d.before(d2) || d.equals(d2)))) {
						return false;
					}
				}
			}
		}
		return true;
	}

	@Override
	public void clearData() {
		super.clearData();
		resetFilterDataVector();
	}

	protected void resetFilterDataVector() {
		if (filterDataVector != null) {
			filterDataVector.clear();
			filterDataVector = null;
			for (final JTableExColumn column : getTableExAllColumns()) {
				column.setFilterExpression(null);
			}
		}
	}

	private Vector<Object> filterDataVector;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void doFilter(final JTableExColumn column, final FilterExpression filterExpression) {
		if (isFixTable()) {
			((JTableEx) dataTbl).doFilter(column, filterExpression);
			return;
		}

		if (column == null) {
			return;
		}
		resetSortOrder();
		column.setFilterExpression(filterExpression);
		if (filterDataVector == null) {
			filterDataVector = backupDataVector(false);
		}

		boolean deleteAll = true;
		final Vector result = new Vector(filterDataVector);
		for (final JTableExColumn col : getTableExAllColumns()) {
			final FilterExpression fe = col.getFilterExpression();
			if (fe == null || fe.item1 == null) {
				continue;
			}
			deleteAll = false;

			final int originalIndex = col.getOriginalIndex();
			final Class<?> pt = col.getBeanPropertyType();

			Vector result2 = new Vector(result);
			result.clear();
			for (final Object data : result2) {
				final Vector rowData = (Vector) data;
				final Object v = rowData.get(originalIndex);
				boolean delete = rowDelete(fe.item1, v, pt);
				if (delete && fe.item2 != null && "or".equalsIgnoreCase(fe.ope)) {
					delete = rowDelete(fe.item2, v, pt);
				}
				if (!delete) {
					result.add(rowData);
				}
			}

			if (fe.item2 != null && "and".equalsIgnoreCase(fe.ope)) {
				result2 = new Vector(result);
				result.clear();
				for (final Object data : result2) {
					final Vector rowData = (Vector) data;
					final Object v = rowData.get(originalIndex);
					if (!rowDelete(fe.item2, v, pt)) {
						result.add(rowData);
					}
				}
			}
		}

		if (deleteAll) {
			resetFilterDataVector();
		}

		final DefaultTableModel tm = (DefaultTableModel) getModel();
		tm.setRowCount(0);
		for (final Object data : result) {
			final Vector<?> rowData = (Vector<?>) data;
			tm.addRow(rowData);
		}
	}

	/***************************** Export and Copy *****************************/

	protected interface ExportData extends Enumeration<Object> {

		int getCount();
	}

	protected ExportData getCVSExportData() {
		if (isFixTable()) {
			return ((JTableEx) dataTbl).getCVSExportData();
		}

		final DefaultTableModel tm = (DefaultTableModel) getModel();
		return new ExportData() {
			private int i = -1;

			@Override
			public int getCount() {
				return tm.getRowCount();
			}

			@Override
			public boolean hasMoreElements() {
				return ++i < getCount();
			}

			@Override
			public Object nextElement() {
				final Map<String, Object> bean = new LinkedHashMap<String, Object>();
				doRowObjects(i, new IRowObject() {
					@Override
					public void doRow(final JTableExColumn column, final Object o) {
						bean.put(column.getColumnName(), o);
					}
				});
				return bean;
			}
		};
	}

	protected void doCopy() {
		final int[] rows = getSelectedRows();
		if (rows == null || rows.length == 0) {
			return;
		}

		final StringWriter sw = new StringWriter();
		final CSVWriter writer = new CSVWriter(sw);
		for (int i = 0; i < rows.length; i++) {
			final ArrayList<String> al = new ArrayList<String>();
			doRowObjects(rows[i], new IRowObject() {
				@Override
				public void doRow(final JTableExColumn column, final Object o) {
					al.add(convertToString(column, o));
				}
			}, true);
			writer.writeNext(al.toArray(new String[al.size()]));
		}
		SwingUtils.copySystemClipboard(sw.toString());
	}

	interface IRowObject {

		void doRow(JTableExColumn column, Object o);
	}

	void doRowObjects(final int row, final IRowObject rowObject) {
		doRowObjects(row, rowObject, false);
	}

	void doRowObjects(final int row, final IRowObject rowObject, final boolean selectedColumn) {
		if (isFixTable()) {
			((JTableEx) dataTbl).doRowObjects(row, rowObject, selectedColumn);
			return;
		}

		for (int jj = (isShowLineNo() ? 1 : 0); jj < fixTbl.getColumnCount(); jj++) {
			if (selectedColumn && !LangUtils.contains(fixTbl.getSelectedColumns(), jj)) {
				continue;
			}
			final JTableExColumn column = fixTbl.getPrivateTableExColumn(jj);
			final Object o = fixTbl.getValueAt(row, column);

			if (rowObject != null) {
				rowObject.doRow(column, o);
			}
		}
		for (int jj = 0; jj < getColumnCount(); jj++) {
			if (selectedColumn && !ArrayUtils.contains(getSelectedColumns(), jj)) {
				continue;
			}
			final JTableExColumn column = getPrivateTableExColumn(jj);
			final Object o = getValueAt(row, column);
			if (rowObject != null) {
				rowObject.doRow(column, o);
			}
		}
	}

	/***************************** Column Function *****************************/

	protected Object function_sum() {
		final int col = mTableExColumn.getModelIndex();
		final DefaultTableModel tm = (DefaultTableModel) getModel();
		double sum = 0;
		for (int i = 0; i < tm.getRowCount(); i++) {
			final Object v = tm.getValueAt(i, col);
			if (v instanceof Number) {
				sum += ((Number) tm.getValueAt(i, col)).doubleValue();
			}
		}
		return sum;
	}

	protected Object function_avg() {
		return (Double) function_sum() / getModel().getRowCount();
	}

	private Object function_mm(final boolean max) {
		final int col = mTableExColumn.getModelIndex();
		final DefaultTableModel tm = (DefaultTableModel) getModel();
		Object mm = null;
		for (int i = 0; i < tm.getRowCount(); i++) {
			final Object v = tm.getValueAt(i, col);
			if (i == 0) {
				mm = v;
				continue;
			}
			if (v instanceof Number) {
				if (max) {
					mm = Math.max(((Number) v).doubleValue(), ((Number) mm).doubleValue());
				} else {
					mm = Math.min(((Number) v).doubleValue(), ((Number) mm).doubleValue());
				}
			} else if (v instanceof Date) {
				if (max) {
					if (((Date) v).after((Date) mm)) {
						mm = v;
					}
				} else {
					if (((Date) v).before((Date) mm)) {
						mm = v;
					}
				}
			}
		}
		if (mm instanceof Date) {
			mm = convertToString(mTableExColumn, mm);
		}
		return mm;
	}

	protected Object function_max() {
		return function_mm(true);
	}

	protected Object function_min() {
		return function_mm(false);
	}

	/***************************** Others *****************************/
	@Override
	protected JComponent createDefaultColumnControl() {
		final JXButton cc = new JXButton();
		cc.setFocusPainted(false);
		cc.setFocusable(false);
		cc.setHideActionText(true);
		cc.setAction(actions.getColumnControlAction());
		return cc;
	}

	protected String getTableStatistics() {
		if (isFixTable()) {
			return ((JTableEx) dataTbl).getTableStatistics();
		}
		final StringBuilder sb = new StringBuilder();
		final int tcount = getModel().getRowCount();
		sb.append("================#(JTableEx.2)================<br>");
		sb.append("#(JTableEx.3) ( ").append(JTableExUtils.colorRed(tcount)).append(" )<br><br>");
		return sb.toString();
	}

	@Override
	protected JAbstractTableEx createFixTable() {
		return new JTableEx();
	}
}
